package com.gmrz.uaf.crypto.internal.handlers;

import java.io.ByteArrayInputStream;
import java.security.GeneralSecurityException;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;

import com.gmrz.uaf.crypto.sm.SM2Utils;
import com.gmrz.util.Convert;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.google.common.base.Strings;
import com.gmrz.uaf.crypto.spi.handlers.AttestationCertHandler;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;

public class X509AttestationCertHandler implements AttestationCertHandler {
    private static final Logger LOG = LogManager.getLogger(X509AttestationCertHandler.class);

    public Certificate decode(byte[] encodedCert) throws CertificateException {
        Certificate cert = null;
        CertificateException e1 = null;
        CertificateException e2 = null;
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            if ((encodedCert != null) && (encodedCert.length > 0)) {
                cert = cf.generateCertificate(new ByteArrayInputStream(encodedCert));
                typeCheck(cert);
            }
        } catch (Exception e) {
            e1 = new CertificateException(e);
        }
        if (null != cert) {
            return cert;
        }
        try {
            cert = SM2Utils.getCert(encodedCert);
        } catch (Exception e) {
            e2 = new CertificateException(e);
        }
        if (null != cert) {
            return cert;
        }
        if (null != e1) {
            throw e1;
        }
        if (null != e2) {
            throw e2;
        }
        return cert;
    }

    public byte[] encode(Certificate cert) throws CertificateException {
        typeCheck(cert);
        return cert.getEncoded();
    }

    public boolean validateCertificate(List<Certificate> certChain, List<Certificate> rootCerts, String aaid)
            throws GeneralSecurityException {
        // CA是通过以 X.509 标准格式放置数据然后对数据进行数字签名来创建证书的服务。
        X509Certificate x509Cert = typeCheck(certChain.get(0));
        // 验证证书是否有效，是否已经过期
        x509Cert.checkValidity();
        // 得到证书的主体
        String subjectDN = x509Cert.getSubjectX500Principal().getName();
        LOG.debug("Attestation certificate is valid." + subjectDN);

//		if (isSelfSigned(x509Cert)) {
//			LOG.debug("Attestation certificate is self signed.");
//		}
        Set trustAnchors = new HashSet();
        for (Certificate cert : rootCerts) {
            X509Certificate x509Certificate = (X509Certificate) cert;
            String x509CertificatePublicKeyString = Convert.toBase64(x509Certificate.getPublicKey().getEncoded());
            for(Certificate ucert : certChain) {
                X509Certificate ux509Cert = (X509Certificate) ucert;
                String x509CertPublicKeyString = Convert.toBase64(ux509Cert.getPublicKey().getEncoded());
                if (x509CertificatePublicKeyString.equals(x509CertPublicKeyString)) {
                    return true;
                }
            }
            // TrustAnchor：该类表示为一个“最守信任的CA”；创建具有指定 X509Certificate  的 TrustAnchor 实例
            TrustAnchor ta = new TrustAnchor((X509Certificate) cert, null);
            trustAnchors.add(ta);
        }
        // 获取“X.509”证书类型的对象，然后在通过generateCertPath（）方法获取CertPath
        CertPath path = CertificateFactory.getInstance("X.509").generateCertPath(certChain);
        // 返回“PKIX”算法的 CertPathValidator，CertPathValidator：验证证书路径（也称为证书链）的类。
        CertPathValidator validator = CertPathValidator.getInstance("PKIX");
        // 设置最受信任的“CA”
        PKIXParameters params = new PKIXParameters(trustAnchors);
        // 设置RevocationEnabled标志
        params.setRevocationEnabled(false);
        // 使用指定的算法参数集验证指定的认证路径。
        validator.validate(path, params);
        LOG.debug("Certificate validated successfully using root certificates as trust anchor.");

        if (!Strings.isNullOrEmpty(subjectDN)) {
            // TODO validateCN(subjectDN, aaid);
        }

        return true;
    }

    void validateCN(String subjectDN, String aaid) throws GeneralSecurityException {
        String cn = null;
        try {
            LOG.debug("subject DN" + subjectDN);
            LdapName ldapName = new LdapName(subjectDN);
            List<Rdn> rdns = ldapName.getRdns();
            for (Rdn rdn : rdns) {
                if (rdn.getType().equalsIgnoreCase("CN")) {
                    cn = (String) rdn.getValue();
                    LOG.debug("cn:" + cn);
                    break;
                }
            }
            if ((!Strings.isNullOrEmpty(cn)) && (cn.contains("#")) && (!StringUtils.containsIgnoreCase(cn, aaid))) {
                LOG.error("CommonName[{}] in SubjectDN doesn't matches AAID[{}]", cn, aaid);
                throw new GeneralSecurityException(UAFErrorCode.PROTOCOL_ATTESTATION_CERT_VALIDATION_FAILED.toString());
            }
        } catch (InvalidNameException ine) {
            LOG.error("SubjectDN is not valid name[{}]", subjectDN);
            throw new GeneralSecurityException(ine);
        }
        LOG.debug("CN in SubjectDN validated successfully.");
    }

    private X509Certificate typeCheck(Certificate c) throws CertificateException {
        if ((c == null) || (!(c instanceof X509Certificate)))
            throw new CertificateException("Certificate type not recongnized:" + c == null ? " null" : c.getType());
        return (X509Certificate) c;
    }

    private boolean isSelfSigned(X509Certificate cert) {
        try {
            PublicKey pubKey = cert.getPublicKey();
            cert.verify(pubKey);
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}
